package com.gitee.wsl.compose.image


import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntSize
import com.gitee.wsl.Lib
import com.gitee.wsl.annotation.ExperimentalApi
import com.gitee.wsl.ioDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext


private enum class SizeBucket {
    Small, Medium, Large;
}

private data class Key(
    val imageUri: String?,
    val contentScale: ContentScale,
    val sizeBucket: SizeBucket,
)

data class MediaArgs(
    val url: String,
    val description: String? = null,
    val contentScale: ContentScale
)

@Composable
fun AsyncRasterImage(
    args: MediaArgs,
    modifier: Modifier,
) {
    var size by remember { mutableStateOf<IntSize?>(null) }
    val imageModifier = modifier.onSizeChanged { size = it }

    val painter = rememberAsyncRasterPainter(
        imageUri = args.url,
        contentScale = args.contentScale,
        size = size,
    )
    if (painter != null) Image(
        modifier = imageModifier,
        painter = painter,
        contentScale = args.contentScale,
        contentDescription = args.description,
    )
    else Box(imageModifier)
}

/*
An implementation of an aysnc raster painter with an in memory LRU cache and temp disk cache.
*/
@OptIn(ExperimentalApi::class)
@Composable
private fun rememberAsyncRasterPainter(
    imageUri: String,
    size: IntSize?,
    contentScale: ContentScale,
): Painter? {
    val cache = LocalPainterCache.current
    val cachedPainter = cache[
        Key(
            imageUri = imageUri,
            contentScale = contentScale,
            sizeBucket = size.toBucket()
        )
    ]
    if (cachedPainter != null) return cachedPainter

//    val inputStream by produceState<InputStream?>(
//        initialValue = null,
//        key1 = imageUri,
//    ) {
//        value = imageUri.toInputStream()
//    }

    val imageLoader  = LocalImageLoader.current

    val painter by produceState<Painter?>(
        initialValue = null,
        //key1 = inputStream,
        key1 = contentScale,
        key2 = size
    ) {
        // Do image manipulation on an IO thread
        val painter = withContext(Lib.ioDispatcher) {
            loadImage(imageLoader, imageUri, size, contentScale)
            //inputStream.toPainter(size, contentScale)
        }
        // Write to value on the main thread
        withContext(Dispatchers.Main) {
            if (painter != null) value = cache.getOrPut(
                Key(
                    imageUri = imageUri,
                    contentScale = contentScale,
                    sizeBucket = size.toBucket()
                )
            ) {
                painter
            }
        }
    }

    // Use the painter created or the smallest existing one
    return painter ?: cache.entries
        .filter { it.key.imageUri == imageUri }
        .minByOrNull { it.key.sizeBucket }
        ?.value
}

internal fun loadImage(imageLoader: ImageLoader, imageUri: String, size: IntSize?, contentScale: ContentScale): Painter? {
    TODO()
}

private fun IntSize?.area() = if (this == null) Int.MAX_VALUE else width * height


private val LocalPainterCache: ProvidableCompositionLocal<MutableMap<Key, Painter?>> =
    staticCompositionLocalOf {
        LinkedHashMap<Key, Painter?>(0, 0.75f)
        /*object : LinkedHashMap<Key, Painter?>(0, 0.75f) {
            override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Key, Painter?>?): Boolean {
                return size > 20
            }
        }*/
    }

private fun IntSize?.toBucket() =
    if (this == null) SizeBucket.Large
    else when (area()) {
        in 0..352 * 240 -> SizeBucket.Small
        in 0..640 * 480 -> SizeBucket.Medium
        else -> SizeBucket.Large
    }